Using R in hydrology (EGU2017 short course)

Instructors: Shaun Harrigan, Katie Smith, Berry Boessenkool and Daniel Klotz
Organizer: Berry Boessenkool, PhD student at Potsdam University (Germany)
Contact: Questions and feedback are welcome via berry-b@gmx.de

These slides and all other course materials can be found at
github.com/brry/rhydro
Download the github course repository with all the materials including the datasets and presentation source code.
This is an R Markdown Notebook.
For discussions, please visit the Hydrology in R Facebook group.
Before running the code blocks below, we suggest to get package installation instructions by running:

source("https://raw.githubusercontent.com/brry/rhydro/master/checkpc.R")


Aim and contents of this workshop

We want to:

We can not:

We have prepared:

 

Before we get started, please let us know your current R knowledge level by filling out the short survey at
bit.ly/knowR


top

Report

Good coding practice, report generation (Rstudio, rmarkdown, R notebook)
Daniel Klotz

Introduction

goals

goals

Why I use R

Why I did not use:

equals

equals

Whats great about R:

  library(ggplot2)
  test_data <- mpg
  test_plot <- ggplot(test_data, aes(displ, hwy, colour = class)) + 
    geom_point()
  test_plot

Why I decided to use R:

pipe

pipe

Previously:

  aggregation_function <- function(x) {
    round(mean(x),2)
  }
  mtcars_subset <- subset(mtcars,hp > 100)
  mtcars_aggregated <- aggregate(. ~ cyl, data = mtcars_subset, FUN = aggregation_function)
  car_data1 <- transform(mtcars_aggregated, kpl = mpg*0.4251)
  print(car_data1)

Now:

library(magrittr)
car_data2 <- 
  mtcars %>%
  subset(hp > 100) %>%
  aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>%
  transform(kpl = mpg %>% multiply_by(0.4251)) %>%
  print

btw: You can integrate other programming languages with ease. Here an example from Yihui Xie for the use of Fortran in Rmarkdown:

  1. Compile Code:
```r
C Fortran test
      subroutine fexp(n, x)
      double precision x
C  output
      integer n, i
C  input value
      do 10 i=1,n
         x=dexp(dcos(dsin(dble(float(i)))))
  10  continue
      return
      end
```
  1. Run Code:
```r
res = .Fortran("fexp", n=100000L, x=0)
str(res)
```

Be happy with the result: > ## List of 2 > ## $ n: int 100000 > ## $ x: num 2.72


Markdown

HTML: HyperText Markdown Language

John Gruber:

John Gruber

Comparison: Markdown vs. Latex Comparison

Rstudio provides cheat-sheets with the most important informations about many of their “favorite” packages & software:

Cheat Sheet

Rmarkdown

In Rstudio:

Rmark1

Rmark2

Rmark2

“Native” Formats:

Much more possible if you adress pandoc directly: pandoc

Information in the text can be automatically updated with the rest of the document: [time for coffee

Examples

Small Websites

Cayman Theme

Cayman Theme

Books (blogdown)

bookdown1 bookdown2

Blogs (hugo)

hugo

Presentations

bookdown1

Widgets

cran-gauge superzip

Apps (Shiny)

shiny shiny2


top

GIS

Using R as GIS (reading a rainfall shapefile + Kriging, sf + leaflet + mapview + OSMscale)
Berry Boessenkool

Shapefiles

Reading shapefiles with maptools::readShapeSpatial and rgdal::readOGR is obsolete.
Instead, use sf::st_read. sf is on CRAN since oct 2016.
Main reaction when using sf: “Wow, that is fast!”
Download the shapefile or better: download the whole github course repository

rain <- sf::st_read("data/PrecBrandenburg/niederschlag.shp")
Reading layer `niederschlag' from data source `D:\Arbeit\2017\rhydro\presentations\data\PrecBrandenburg\niederschlag.shp' using driver `ESRI Shapefile'
converted into: POLYGON
Simple feature collection with 277 features and 1 field
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 3250175 ymin: 5690642 xmax: 3483631 ymax: 5932731
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=0 +lon_0=15 +k=0.9996 +x_0=3500000 +y_0=0 +ellps=GRS80 +units=m +no_defs

Central points of rainfall Thiessen polygons

centroids <- sf::st_centroid(rain)
centroids <- sf::st_coordinates(centroids)
centroids <- as.data.frame(centroids)

top

Plotting, maps

Static plot:

plot(rain[,1])

Static map:

prj <- sf::st_crs(rain)$proj4string
cent_ll <- OSMscale::projectPoints(Y,X, data=centroids, to=OSMscale::pll(), from=prj)
#map_static <- OSMscale::pointsMap(y,x, cent_ll, fx=0.08, type="maptoolkit-topo", zoom=6)
#save(map_static, file="data/map_static.Rdata")
load("data/map_static.Rdata")
OSMscale::pointsMap(y,x, cent_ll, map=map_static)
Done. Now plotting...

Interactive map:

library(leaflet)
cent_ll$info <- paste0(sample(letters,nrow(cent_ll),TRUE), ", ", round(cent_ll$x,2), 
                       ", ", round(cent_ll$y,2))
leaflet(cent_ll) %>% addTiles() %>% addCircleMarkers(lng=~x, lat=~y, popup=~info)

Interactive map of shapefile:

# make sure to have installed the development version of mapview: 
# devtools::install_github("environmentalinformatics-marburg/mapview", ref = "develop")
library(berryFunctions) # classify, seqPal
col <- seqPal(n=100, colors=c("red","yellow","blue"))[classify(rain$P1)$index]
mapview::mapview(rain, col.regions=col)

top

Kriging

Plot original points colored by third dimension:

pcol <- colorRampPalette(c("red","yellow","blue"))(50)
x <- centroids$X # use cent_ll$x for projected data
y <- centroids$Y
berryFunctions::colPoints(x, y, rain$P1, add=FALSE, col=pcol)

Calculate the variogram and fit a semivariance curve

library(geoR)
geoprec <- as.geodata(cbind(x,y,rain$P1))
vario <- variog(geoprec, max.dist=130000) # other maxdist for lat-lon data
variog: computing omnidirectional variogram
fit <- variofit(vario)
variofit: covariance model used is matern 
variofit: weights used: npairs 
variofit: minimisation function used: optim 
initial values not provided - running the default search
variofit: searching for best initial value ... selected values:
              sigmasq   phi        tausq kappa
initial.value "1325.81" "19999.05" "0"   "0.5"
status        "est"     "est"      "est" "fix"
loss value: 104819060.429841 
plot(vario)
lines(fit)

Determine a useful resolution (keep in mind that computing time rises exponentially with grid size)

# distance to closest other point:
d <- sapply(1:length(x), function(i)
            min(berryFunctions::distance(x[i], y[i], x[-i], y[-i])) )
# for lat-long data use (2017-Apr only available in github version of OSMscale)
# d <- OSMscale::maxEarthDist(y,x, data=cent_ll, fun=min)
hist(d/1000, breaks=20, main="distance to closest gauge [km]")

mean(d/1000) # 8 km
[1] 8.165713

Perform kriging on a grid with that resolution

res <- 1000 # 1 km, since stations are 8 km apart on average
grid <- expand.grid(seq(min(x),max(x),res),
                    seq(min(y),max(y),res))
krico <- krige.control(type.krige="OK", obj.model=fit)
#krobj <- krige.conv(geoprec, loc=grid, krige=krico)
#save(krobj, file="data/krobj.Rdata")
load("data/krobj.Rdata") # line above is too slow for recreation each time

Plot the interpolated values with image or an equivalent function (see Rclick 4.15) and add contour lines.

par(mar=c(0,3,0,3))
geoR:::image.kriging(krobj, col=pcol)
colPoints(x, y, rain$P1, col=pcol, legargs=list(horiz=F, title="Prec",y1=0.1,x1=0.9))
points(x,y)
plot(rain, col=NA, add=TRUE)


top

Discharge

River discharge time-series visualisation and extreme value statistics (animation + extremeStat)
Berry Boessenkool

Read, plot and aggregate data

Datasets from http://nrfa.ceh.ac.uk/data/station/meanflow/39072
Download discharge1, discharge2_xxx, discharge3_xxx, or better: download the whole github course repository

Read and transform data

Q <- read.table("data/discharge39072.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q) <- c("date","Royal_Windsor_Park")
Q$date <- as.Date(Q$date, format="%Y-%m-%d")

Examine data

head(Q)
str(Q)
'data.frame':   13222 obs. of  2 variables:
 $ date              : Date, format: "1979-07-20" "1979-07-21" "1979-07-22" "1979-07-23" ...
 $ Royal_Windsor_Park: num  33.4 32.5 33.1 30.6 30.1 ...

Simple time series plot

plot(Q, type="l", col="blue")

Publication-ready graphics

png("DischargeVis.png", width=20, height=10, units="cm", res=500)
#pdf("DischargeVis.pdf", width=20/2.5, height=10/2.5) # vector graph
par(mar=c(3.5,3.5,2.5,0.2), mgp=c(2.3,0.7,0), las=1)
plot(Q, type="l", col="blue", main="NRFA: Thames\nRoyal Windsor Park",
     xlab="Date", ylab="Discharge  [m\U{00B3}/s]")
dev.off()
null device 
          1 

Annual maxima, German hydrological year split at Oct 31

Q$hydyear <- as.numeric(format(Q$date+61, "%Y"))
annmax <- tapply(Q$Royal_Windsor_Park, Q$hydyear, max, na.rm=TRUE)
annmax <- annmax[-1]
hydyear <- as.numeric(names(annmax)) 
plot(hydyear, annmax, type="o", las=1)

Extreme value statistics

library(extremeStat)
Paket <U+393C><U+3E31>extremeStat<U+393C><U+3E32> wurde unter R Version 3.3.3 erstellt# Loaded extremeStat 1.3.0 (2017-01-26). Package restructured since 0.6.0 (2016-12-13).
# Computing functions don't plot anymore and some are renamed. See help('extremeStat-deprecated')
dlf <- distLfit(annmax)
Note in distLfit: dat was not a vector.
distLfit execution took 0.1 seconds.
plotLfit(dlf)

plotLfit(dlf, cdf=TRUE)

dle <- distLextreme(dlf=dlf, RPs=c(5,10,50,100), gpd=FALSE)
plotLextreme(dle)

Logarithmic plot with many fitted distribution functions

plotLextreme(dle, nbest=16, log=TRUE)

Return values (discharge estimates for given return periods)

dlf$returnlev
NULL

In reality, please use non-stationary EVS!

Uncertainty band for Wakeby distribution fit estimate

dle_boot <- distLexBoot(dle, n=10, nbest=1)

   |+++++                                             | 10% ~00s          
   |++++++++++                                        | 20% ~00s          
   |+++++++++++++++                                   | 30% ~00s          
   |++++++++++++++++++++                              | 40% ~00s          
   |+++++++++++++++++++++++++                         | 50% ~00s          
   |++++++++++++++++++++++++++++++                    | 60% ~00s          
   |+++++++++++++++++++++++++++++++++++               | 70% ~00s          
   |++++++++++++++++++++++++++++++++++++++++         | 80% ~00s          
   |+++++++++++++++++++++++++++++++++++++++++++++    | 90% ~00s          
   |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed = 00s
plotLexBoot(dle_boot, distcol="green")

More details in the package vignette

vignette("extremeStat")

Animated movie

Read data from several discharge stations

if(FALSE){
Q2 <- read.table("data/discharge_____.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q2) <- c("date","dummy1");  Q2$date <- as.Date(Q2$date, format="%Y-%m-%d")
Q3 <- read.table("data/discharge_____.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q3) <- c("date","dummy2");  Q3$date <- as.Date(Q3$date, format="%Y-%m-%d")
}
Q2 <- Q ; Q2[,2] <- Q2[,2] + 100 ; Q2 <- Q2[-(1:100),]
Q3 <- Q ; Q3[,2] <- Q3[,2] -  50 ; Q3 <- head(Q3, -365*3)
colnames(Q2)[2] <- "dummy1"; colnames(Q3)[2] <- "dummy2"

Merge datasets

dis <- Reduce(function(...) merge(..., all=T), list(Q,Q2,Q3))

Code to generate one movie slice

library(berryFunctions) # for lim0, monthAxis, textField, etc
scene <- function(i, vlcnote=TRUE, cex=1.2)
{
 sel <- 0:120
 dis2 <- dis[i + sel, ]
 stat <- c("Royal_Windsor_Park", "dummy1", "dummy2")
 col <- c("red", "blue", "orange")
 names(col) <- stat
 plot(dis2$date,dis2$Royal_Windsor_Park, type="n", ylim=lim0(500),  las=1, 
      xaxt="n", yaxt="n", cex.lab=cex, xlab="", 
      ylab="Discharge  [m\U{00B3}/s]", xaxs="i")
 axis(2, cex.axis=cex, las=1)
 Sys.setlocale("LC_TIME", "C")
 monthAxis(midmonth=TRUE, format="%b\n%Y", cex=cex, mgp=c(3,3.5,0))
 abline(h=1:6*100, v=dis2$date[format(dis2$date,"%d")=="01"], col=8)
 for(s in stat) lines(dis2$date, dis2[,s], lwd=4, col=col[s])
 xi <- seqR(sel,len=length(stat)+2)[-1]
 xi <- head(xi, -1)
 textField(dis2$date[xi], diag(as.matrix(dis2[xi,stat])), stat, cex=cex, col=col)
 box()
 if(vlcnote) mtext("VLC: 'e': single frame forward\n'SHIFT+LEFT': few seconds back",
                   side=3, line=-9, outer=TRUE, adj=0.95, cex=cex)
}
par(mar=c(5,5,0.5,0.5), mgp=c(3,0.7,0))
scene(150)

Actual movie

library(animation)
saveVideo({par(mar=c(6,8,1,1), mgp=c(5.5,0.7,0))
 dummy <- pbsapply(seq(0, by=3, len=50), scene, cex=2); rm(dummy)
}, video.name="Qmovie.mp4", interval=0.1, ffmpeg="/usr/bin/ffmpeg", 
ani.width=7*200, ani.height=5*200)

Open video in default mp4player


top

Hydmod

Hydrological modelling with airGR
Katie Smith


top

EDA

Exploratory Data Analysis including flow duration curve and trend analysis on time-series
Shaun Harrigan


top

Discussion

Please give us feedback at bit.ly/feedbackR

For discussions, please use the Hydrology in R Facebook group.

LS0tDQp0aXRsZTogIlJoeWRybyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KaWYgKGtuaXRyOjo6aXNfaHRtbF9vdXRwdXQoKSkgew0KICBrbml0cjo6b3B0c19jaHVuayRzZXQoZXZhbCA9IEZBTFNFKQ0KfQ0KYGBgDQoNCiMgVXNpbmcgUiBpbiBoeWRyb2xvZ3kgKEVHVTIwMTcgc2hvcnQgY291cnNlKQ0KDQoqSW5zdHJ1Y3RvcnMqOiBTaGF1biBIYXJyaWdhbiwgS2F0aWUgU21pdGgsIEJlcnJ5IEJvZXNzZW5rb29sIGFuZCBEYW5pZWwgS2xvdHogIA0KKk9yZ2FuaXplcio6IEJlcnJ5IEJvZXNzZW5rb29sLCBQaEQgc3R1ZGVudCBhdCBQb3RzZGFtIFVuaXZlcnNpdHkgKEdlcm1hbnkpICANCipDb250YWN0KjogUXVlc3Rpb25zIGFuZCBmZWVkYmFjayBhcmUgd2VsY29tZSB2aWEgPGJlcnJ5LWJAZ214LmRlPg0KDQpUaGVzZSBzbGlkZXMgYW5kIGFsbCBvdGhlciBjb3Vyc2UgbWF0ZXJpYWxzIGNhbiBiZSBmb3VuZCBhdCAgDQo8Zm9udCBzaXplPSI2Ij5bZ2l0aHViLmNvbS9icnJ5L3JoeWRyb10oaHR0cHM6Ly9naXRodWIuY29tL2Jycnkvcmh5ZHJvKTwvZm9udD4gICANCltEb3dubG9hZCB0aGUgZ2l0aHViIGNvdXJzZSByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vYXJjaGl2ZS9tYXN0ZXIuemlwKQ0Kd2l0aCBhbGwgdGhlIG1hdGVyaWFscyBpbmNsdWRpbmcgdGhlIGRhdGFzZXRzIGFuZCBwcmVzZW50YXRpb24gc291cmNlIGNvZGUuICANClRoaXMgaXMgYW4gW1IgTWFya2Rvd24gTm90ZWJvb2tdKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vcl9ub3RlYm9va3MuaHRtbCkuICANCkZvciBkaXNjdXNzaW9ucywgcGxlYXNlIHZpc2l0IHRoZSANCltIeWRyb2xvZ3kgaW4gUiBGYWNlYm9vayBncm91cF0oaHR0cHM6Ly93d3cuZmFjZWJvb2suY29tL2dyb3Vwcy8xMTMwMjE0Nzc3MTIzOTA5LykuICANCkJlZm9yZSBydW5uaW5nIHRoZSBjb2RlIGJsb2NrcyBiZWxvdywgd2Ugc3VnZ2VzdCB0byBnZXQgcGFja2FnZSBpbnN0YWxsYXRpb24gaW5zdHJ1Y3Rpb25zIGJ5IHJ1bm5pbmc6DQpgYGBSDQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9icnJ5L3JoeWRyby9tYXN0ZXIvY2hlY2twYy5SIikNCmBgYA0KDQpcDQoNCioqQWltIGFuZCBjb250ZW50cyBvZiB0aGlzIHdvcmtzaG9wKioNCg0KV2Ugd2FudCB0bzogIA0KDQoqIFNob3cgb2ZmIGhvdyBhd2Vzb21lIFIgaXMgZm9yIGh5ZHJvbG9neSAoaXQncyBSLXNvbWUhXl4pICANCiogQ29udmluY2UgeW91IHRvIHN0YXJ0IG9yIGNvbnRpbnVlIHVzaW5nIFIgIA0KKiBQcm92aWRlIGFsbCB0aGUgY29kZSBmb3IgeW91IGFzIGEgc3RhcnRpbmcgcG9pbnQNCg0KV2UgY2FuIG5vdDogIA0KDQoqIFRlYWNoIHlvdSBhY3R1YWwgUiBjb2RpbmcgKDkwIG1pbnMgaXMgdG9vIHNob3J0IGZvciBhIHR1dG9yaWFsKQ0KDQpXZSBoYXZlIHByZXBhcmVkOg0KDQoqIFtHb29kIGNvZGluZyBwcmFjdGljZSwgcmVwb3J0IGdlbmVyYXRpb25dKCNyZXBvcnQpIChSc3R1ZGlvLCBgcm1hcmtkb3duYCwgUiBub3RlYm9vaykNCiogW1VzaW5nIFIgYXMgR0lTXSgjZ2lzKSAocmVhZGluZyBhIHJhaW5mYWxsIHNoYXBlZmlsZSArIEtyaWdpbmcsIGBzZmAgKyBgbGVhZmxldGAgKyBgbWFwdmlld2AgKyBgT1NNc2NhbGVgKQ0KKiBbUml2ZXIgZGlzY2hhcmdlIHRpbWUtc2VyaWVzXSgjZGlzY2hhcmdlKSB2aXN1YWxpc2F0aW9uIGFuZCBleHRyZW1lIHZhbHVlIHN0YXRpc3RpY3MgKGBhbmltYXRpb25gICsgYGV4dHJlbWVTdGF0YCkNCiogW0h5ZHJvbG9naWNhbCBtb2RlbGxpbmddKCNoeWRtb2QpIHdpdGggYGFpckdSYA0KKiBbRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpc10oI2VkYSkgaW5jbHVkaW5nIGZsb3cgZHVyYXRpb24gY3VydmUgYW5kIHRyZW5kIGFuYWx5c2lzIG9uIHRpbWUtc2VyaWVzDQoNClwgDQoNCkJlZm9yZSB3ZSBnZXQgc3RhcnRlZCwgcGxlYXNlIGxldCB1cyBrbm93IHlvdXIgY3VycmVudCBSIGtub3dsZWRnZSBsZXZlbCBieSBmaWxsaW5nIG91dCB0aGUgc2hvcnQgc3VydmV5IGF0ICANCjxmb250IHNpemU9IjYiPltiaXQubHkva25vd1JdKGh0dHBzOi8vYml0Lmx5L2tub3dSKTwvZm9udD4gDQoNClwNCg0KW3RvcF0oI3RvcCkNCg0KIyBSZXBvcnQNCkdvb2QgY29kaW5nIHByYWN0aWNlLCByZXBvcnQgZ2VuZXJhdGlvbiAoUnN0dWRpbywgYHJtYXJrZG93bmAsIFIgbm90ZWJvb2spICANCioqRGFuaWVsIEtsb3R6KioNCg0KIyMgSW50cm9kdWN0aW9uDQoNCiFbZ29hbHNdKGRhbmllbC9pbnRyby9nb2Fscy5qcGVnKQ0KDQojIyMgV2h5IEkgdXNlIFINCg0KV2h5IEkgZGlkIG5vdCB1c2U6IA0KDQohW2VxdWFsc10oZGFuaWVsL2ludHJvL2VxdWFscy5qcGVnKQ0KDQoNCldoYXRzIGdyZWF0IGFib3V0IFI6IA0KYGBge3J9DQogIGxpYnJhcnkoZ2dwbG90MikNCiAgdGVzdF9kYXRhIDwtIG1wZw0KICB0ZXN0X3Bsb3QgPC0gZ2dwbG90KHRlc3RfZGF0YSwgYWVzKGRpc3BsLCBod3ksIGNvbG91ciA9IGNsYXNzKSkgKyANCiAgICBnZW9tX3BvaW50KCkNCiAgdGVzdF9wbG90DQpgYGANCg0KV2h5IEkgZGVjaWRlZCB0byB1c2UgUjogDQoNCiFbcGlwZV0oZGFuaWVsL2ludHJvL21hZ3JpdHRyLmpwZWcpDQoNClByZXZpb3VzbHk6DQpgYGB7cn0NCiAgYWdncmVnYXRpb25fZnVuY3Rpb24gPC0gZnVuY3Rpb24oeCkgew0KICAgIHJvdW5kKG1lYW4oeCksMikNCiAgfQ0KICBtdGNhcnNfc3Vic2V0IDwtIHN1YnNldChtdGNhcnMsaHAgPiAxMDApDQogIG10Y2Fyc19hZ2dyZWdhdGVkIDwtIGFnZ3JlZ2F0ZSguIH4gY3lsLCBkYXRhID0gbXRjYXJzX3N1YnNldCwgRlVOID0gYWdncmVnYXRpb25fZnVuY3Rpb24pDQogIGNhcl9kYXRhMSA8LSB0cmFuc2Zvcm0obXRjYXJzX2FnZ3JlZ2F0ZWQsIGtwbCA9IG1wZyowLjQyNTEpDQogIHByaW50KGNhcl9kYXRhMSkNCmBgYA0KDQpOb3c6DQpgYGB7cn0NCmxpYnJhcnkobWFncml0dHIpDQpjYXJfZGF0YTIgPC0gDQogIG10Y2FycyAlPiUNCiAgc3Vic2V0KGhwID4gMTAwKSAlPiUNCiAgYWdncmVnYXRlKC4gfiBjeWwsIGRhdGEgPSAuLCBGVU4gPSAuICU+JSBtZWFuICU+JSByb3VuZCgyKSkgJT4lDQogIHRyYW5zZm9ybShrcGwgPSBtcGcgJT4lIG11bHRpcGx5X2J5KDAuNDI1MSkpICU+JQ0KICBwcmludA0KYGBgDQoNCg0KKipidHc6KioNCllvdSBjYW4gaW50ZWdyYXRlIG90aGVyIHByb2dyYW1taW5nIGxhbmd1YWdlcyB3aXRoIGVhc2UuIEhlcmUgYW4gZXhhbXBsZSBmcm9tIA0KW1lpaHVpIFhpZV0oaHR0cHM6Ly95aWh1aS5uYW1lL2tuaXRyL2RlbW8vZW5naW5lcy8pIGZvciB0aGUgdXNlIG9mIGBGb3J0cmFuYCANCmluIFJtYXJrZG93bjoNCg0KMS4gQ29tcGlsZSBDb2RlOg0KICAgIGBgYHtyIGNvbXBmb3J0LCBlbmdpbmU9J2ZvcnRyYW4nLCByZXN1bHRzPSdoaWRlJywgZXZhbD1GQUxTRX0NCiAgICBDIEZvcnRyYW4gdGVzdA0KICAgICAgICAgIHN1YnJvdXRpbmUgZmV4cChuLCB4KQ0KICAgICAgICAgIGRvdWJsZSBwcmVjaXNpb24geA0KICAgIEMgIG91dHB1dA0KICAgICAgICAgIGludGVnZXIgbiwgaQ0KICAgIEMgIGlucHV0IHZhbHVlDQogICAgICAgICAgZG8gMTAgaT0xLG4NCiAgICAgICAgICAgICB4PWRleHAoZGNvcyhkc2luKGRibGUoZmxvYXQoaSkpKSkpDQogICAgICAxMCAgY29udGludWUNCiAgICAgICAgICByZXR1cm4NCiAgICAgICAgICBlbmQNCiAgICBgYGANCg0KMi4gUnVuIENvZGU6DQogICAgYGBge3IgdGVzdGZvcnQsIGNvbGxhcHNlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCiAgICByZXMgPSAuRm9ydHJhbigiZmV4cCIsIG49MTAwMDAwTCwgeD0wKQ0KICAgIHN0cihyZXMpDQogICAgYGBgDQoNCkJlIGhhcHB5IHdpdGggdGhlIHJlc3VsdDogDQo+ICAgICBgIyMgTGlzdCBvZiAyYA0KPiAgICAgYCMjICAkIG46IGludCAxMDAwMDBgDQo+ICAgICBgIyMgICQgeDogbnVtIDIuNzJgDQoNCi0tLQ0KDQojIyBNYXJrZG93biANCkhUKipNKipMOiBIeXBlclRleHQgKipNYXJrZG93bioqIExhbmd1YWdlDQoNCkpvaG4gR3J1YmVyOg0KDQpbIVtKb2huIEdydWJlcl0oZGFuaWVsL2ludHJvL0pvaG5fR3J1YmVyX3dpa2kuanBlZyldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0pvaG5fR3J1YmVyKQ0KDQoNCkNvbXBhcmlzb246IE1hcmtkb3duIHZzLiBMYXRleCANClshW0NvbXBhcmlzb25dKGRhbmllbC9pbnRyby95aWh1aV9sYXRleC12cy1tYXJrZG93bi5wbmcpXShodHRwczovL3lvdXR1LmJlLzJ5dlcwT183eE9nKQ0KDQoNClJzdHVkaW8gcHJvdmlkZXMgY2hlYXQtc2hlZXRzIHdpdGggdGhlIG1vc3QgaW1wb3J0YW50IGluZm9ybWF0aW9ucyBhYm91dCBtYW55IA0Kb2YgdGhlaXIgImZhdm9yaXRlIiBwYWNrYWdlcyAmIHNvZnR3YXJlOg0KDQpbIVtDaGVhdCBTaGVldF0oZGFuaWVsL2ludHJvL1JtYXJrZG93bl9jaGVhdHNoZWV0LnBuZyldKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pDQoNCiMgUm1hcmtkb3duDQpJbiBSc3R1ZGlvOiANCg0KPGRpdiBhbGlnbj0iY2VudGVyIj4NCiAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL3JlcG9ydHMvUm1hcmsxLnBuZyIgYWx0PSJSbWFyazEiIC8+DQo8L2Rpdj4NCg0KPGRpdiBhbGlnbj0iY2VudGVyIj4NCiAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL3JlcG9ydHMvUm1hcmsyLnBuZyIgYWx0PSJSbWFyazIiIC8+DQo8L2Rpdj4NCg0KPGRpdiBhbGlnbj0iY2VudGVyIj4NCiAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL3JlcG9ydHMvUm1hcmszLnBuZyIgYWx0PSJSbWFyazIiIC8+DQo8L2Rpdj4NCg0KIk5hdGl2ZSIgRm9ybWF0czoNCg0KLSBbaHRtbF0oZGFuaWVsL3Nob3cvUm1hcmsuaHRtbCkgDQotIFtwZGZdKGRhbmllbC9zaG93L1JtYXJrLnBkZikgDQotIFt3b3JkXShkYW5pZWwvc2hvdy9SbWFyay5kb2NzKQ0KDQpNdWNoIG1vcmUgcG9zc2libGUgaWYgeW91IGFkcmVzcyBwYW5kb2MgZGlyZWN0bHk6IA0KWyFbcGFuZG9jXShkYW5pZWwvcmVwb3J0cy9wYW5kb2NfZGlhZ3JhbS5qcGcpXShodHRwOi8vcGFuZG9jLm9yZy8pDQoNCkluZm9ybWF0aW9uIGluIHRoZSB0ZXh0IGNhbiBiZSBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgd2l0aCB0aGUgcmVzdCBvZiB0aGUgDQpkb2N1bWVudDoNClshW3RpbWUgZm9yIGNvZmZlZV0oZGFuaWVsL3JlcG9ydHMvY29mZmVlLnBuZykNCg0KIyMjIEV4YW1wbGVzDQoNCiMjIyMgU21hbGwgV2Vic2l0ZXMNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogIDxhIGhyZWYgPSAiaHR0cDovL3JzdHVkaW8uZ2l0aHViLmlvL3R1ZnRlLyI+DQogICAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL2V4YW1wbGVzL3N3X3R1ZnRlLnBuZyIgYWx0PSJDYXltYW4gVGhlbWUiIC8+DQogIDwvYT4NCjwvZGl2Pg0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICA8YSBocmVmID0gImh0dHA6Ly95aXh1YW4uY29zLm5hbWUvcHJldHR5ZG9jL2NheW1hbi5odG1sIj4NCiAgICA8aW1nIHdpZHRoPSI2MDBweCIgc3JjPSJkYW5pZWwvZXhhbXBsZXMvc3dfY2F5bWFuLnBuZyIgYWx0PSJDYXltYW4gVGhlbWUiIC8+DQogIDwvYT4NCjwvZGl2Pg0KDQojIyMjIEJvb2tzIChibG9nZG93bikNClshW2Jvb2tkb3duMV0oZGFuaWVsL2V4YW1wbGVzL2Jvb2tkb3duXzEucG5nKV0oaHR0cHM6Ly9ib29rZG93bi5vcmcvKQ0KWyFbYm9va2Rvd24yXShkYW5pZWwvZXhhbXBsZXMvYm9va2Rvd25fMi5wbmcpXShodHRwczovL2Jvb2tkb3duLm9yZy8pDQoNCiMjIyBCbG9ncyAoaHVnbykNClshW2h1Z29dKGRhbmllbC9leGFtcGxlcy9ibG9nc19odWdvMS5wbmcpXShodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ibG9nZG93bi8pDQoNCiMjIyMgUHJlc2VudGF0aW9ucw0KWyFbYm9va2Rvd24xXShkYW5pZWwvZXhhbXBsZXMvcHJlc2VudGF0aW9uc18xLnBuZyldKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vcmV2ZWFsanNfcHJlc2VudGF0aW9uX2Zvcm1hdC5odG1sKQ0KDQojIyMjIFdpZGdldHMgDQpbIVtjcmFuLWdhdWdlXShkYW5pZWwvZXhhbXBsZXMvd2lkZ2V0c18xLnBuZyldKGh0dHBzOi8vZ2FsbGVyeS5zaGlueWFwcHMuaW8vY3Jhbi1nYXVnZS8pDQpbIVtzdXBlcnppcF0oZGFuaWVsL2V4YW1wbGVzL3dpZGdldHNfMi5wbmcpXShodHRwczovL3NoaW55LnJzdHVkaW8uY29tL2dhbGxlcnkvc3VwZXJ6aXAtZXhhbXBsZS5odG1sKQ0KDQojIyMjIEFwcHMgKFNoaW55KSANClshW3NoaW55XShkYW5pZWwvZXhhbXBsZXMvc2hpbnlfMS5qcGVnKV0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS9hdXRob3Jpbmdfc2hpbnkuaHRtbCkNClshW3NoaW55Ml0oZGFuaWVsL2V4YW1wbGVzL3NoaW55XzJfZ2FsbGVyeS5wbmcpXShodHRwczovL3NoaW55LnJzdHVkaW8uY29tL2dhbGxlcnkvc3VwZXJ6aXAtZXhhbXBsZS5odG1sKQ0KDQoNClwNClt0b3BdKCN0b3ApDQoNCiMgR0lTDQpVc2luZyBSIGFzIEdJUyAocmVhZGluZyBhIHJhaW5mYWxsIHNoYXBlZmlsZSArIEtyaWdpbmcsIGBzZmAgKyBgbGVhZmxldGAgKyBgbWFwdmlld2AgKyBgT1NNc2NhbGVgKSAgDQoqKkJlcnJ5IEJvZXNzZW5rb29sKioNCg0KIyMjIFNoYXBlZmlsZXMNCg0KUmVhZGluZyBzaGFwZWZpbGVzIHdpdGggYG1hcHRvb2xzOjpyZWFkU2hhcGVTcGF0aWFsYCBhbmQgYHJnZGFsOjpyZWFkT0dSYCBpcyBvYnNvbGV0ZS4gIA0KSW5zdGVhZCwgdXNlIGBzZjo6c3RfcmVhZGAuIGBzZmAgaXMgb24gQ1JBTiBzaW5jZSBvY3QgMjAxNi4gIA0KTWFpbiByZWFjdGlvbiB3aGVuIHVzaW5nIHNmOiAiV293LCB0aGF0IGlzIGZhc3QhIiAgDQpbRG93bmxvYWQgdGhlIHNoYXBlZmlsZV0oaHR0cHM6Ly9taW5oYXNrYW1hbC5naXRodWIuaW8vRG93bkdpdC8jL2hvbWU/dXJsPWh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby90cmVlL21hc3Rlci9wcmVzZW50YXRpb25zL2RhdGEvUHJlY0JyYW5kZW5idXJnKSANCm9yIGJldHRlcjogW2Rvd25sb2FkIHRoZSB3aG9sZSBnaXRodWIgY291cnNlIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby9hcmNoaXZlL21hc3Rlci56aXApDQoNCmBgYHtyfQ0KcmFpbiA8LSBzZjo6c3RfcmVhZCgiZGF0YS9QcmVjQnJhbmRlbmJ1cmcvbmllZGVyc2NobGFnLnNocCIpDQpgYGANCg0KQ2VudHJhbCBwb2ludHMgb2YgcmFpbmZhbGwgVGhpZXNzZW4gcG9seWdvbnMNCmBgYHtyfQ0KY2VudHJvaWRzIDwtIHNmOjpzdF9jZW50cm9pZChyYWluKQ0KY2VudHJvaWRzIDwtIHNmOjpzdF9jb29yZGluYXRlcyhjZW50cm9pZHMpDQpjZW50cm9pZHMgPC0gYXMuZGF0YS5mcmFtZShjZW50cm9pZHMpDQpgYGANCg0KW3RvcF0oI3RvcCkNCg0KIyMjIFBsb3R0aW5nLCBtYXBzDQoNClN0YXRpYyBwbG90Og0KYGBge3J9DQpwbG90KHJhaW5bLDFdKQ0KYGBgDQoNClN0YXRpYyBtYXA6DQpgYGB7cn0NCnByaiA8LSBzZjo6c3RfY3JzKHJhaW4pJHByb2o0c3RyaW5nDQpjZW50X2xsIDwtIE9TTXNjYWxlOjpwcm9qZWN0UG9pbnRzKFksWCwgZGF0YT1jZW50cm9pZHMsIHRvPU9TTXNjYWxlOjpwbGwoKSwgZnJvbT1wcmopDQojbWFwX3N0YXRpYyA8LSBPU01zY2FsZTo6cG9pbnRzTWFwKHkseCwgY2VudF9sbCwgZng9MC4wOCwgdHlwZT0ibWFwdG9vbGtpdC10b3BvIiwgem9vbT02KQ0KI3NhdmUobWFwX3N0YXRpYywgZmlsZT0iZGF0YS9tYXBfc3RhdGljLlJkYXRhIikNCmxvYWQoImRhdGEvbWFwX3N0YXRpYy5SZGF0YSIpDQpPU01zY2FsZTo6cG9pbnRzTWFwKHkseCwgY2VudF9sbCwgbWFwPW1hcF9zdGF0aWMpDQpgYGANCg0KSW50ZXJhY3RpdmUgbWFwOg0KYGBge3J9DQpsaWJyYXJ5KGxlYWZsZXQpDQpjZW50X2xsJGluZm8gPC0gcGFzdGUwKHNhbXBsZShsZXR0ZXJzLG5yb3coY2VudF9sbCksVFJVRSksICIsICIsIHJvdW5kKGNlbnRfbGwkeCwyKSwgDQogICAgICAgICAgICAgICAgICAgICAgICIsICIsIHJvdW5kKGNlbnRfbGwkeSwyKSkNCmxlYWZsZXQoY2VudF9sbCkgJT4lIGFkZFRpbGVzKCkgJT4lIGFkZENpcmNsZU1hcmtlcnMobG5nPX54LCBsYXQ9fnksIHBvcHVwPX5pbmZvKQ0KYGBgDQoNCkludGVyYWN0aXZlIG1hcCBvZiBzaGFwZWZpbGU6DQpgYGB7cn0NCiMgbWFrZSBzdXJlIHRvIGhhdmUgaW5zdGFsbGVkIHRoZSBkZXZlbG9wbWVudCB2ZXJzaW9uIG9mIG1hcHZpZXc6IA0KIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImVudmlyb25tZW50YWxpbmZvcm1hdGljcy1tYXJidXJnL21hcHZpZXciLCByZWYgPSAiZGV2ZWxvcCIpDQpsaWJyYXJ5KGJlcnJ5RnVuY3Rpb25zKSAjIGNsYXNzaWZ5LCBzZXFQYWwNCmNvbCA8LSBzZXFQYWwobj0xMDAsIGNvbG9ycz1jKCJyZWQiLCJ5ZWxsb3ciLCJibHVlIikpW2NsYXNzaWZ5KHJhaW4kUDEpJGluZGV4XQ0KbWFwdmlldzo6bWFwdmlldyhyYWluLCBjb2wucmVnaW9ucz1jb2wpDQpgYGANCg0KW3RvcF0oI3RvcCkNCg0KIyMjIEtyaWdpbmcNCg0KUGxvdCBvcmlnaW5hbCBwb2ludHMgY29sb3JlZCBieSB0aGlyZCBkaW1lbnNpb246DQpgYGB7cn0NCnBjb2wgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJyZWQiLCJ5ZWxsb3ciLCJibHVlIikpKDUwKQ0KeCA8LSBjZW50cm9pZHMkWCAjIHVzZSBjZW50X2xsJHggZm9yIHByb2plY3RlZCBkYXRhDQp5IDwtIGNlbnRyb2lkcyRZDQpiZXJyeUZ1bmN0aW9uczo6Y29sUG9pbnRzKHgsIHksIHJhaW4kUDEsIGFkZD1GQUxTRSwgY29sPXBjb2wpDQpgYGANCg0KQ2FsY3VsYXRlIHRoZSB2YXJpb2dyYW0gYW5kIGZpdCBhIHNlbWl2YXJpYW5jZSBjdXJ2ZQ0KYGBge3J9DQpsaWJyYXJ5KGdlb1IpDQpnZW9wcmVjIDwtIGFzLmdlb2RhdGEoY2JpbmQoeCx5LHJhaW4kUDEpKQ0KdmFyaW8gPC0gdmFyaW9nKGdlb3ByZWMsIG1heC5kaXN0PTEzMDAwMCkgIyBvdGhlciBtYXhkaXN0IGZvciBsYXQtbG9uIGRhdGENCmZpdCA8LSB2YXJpb2ZpdCh2YXJpbykNCnBsb3QodmFyaW8pDQpsaW5lcyhmaXQpDQpgYGANCg0KRGV0ZXJtaW5lIGEgdXNlZnVsIHJlc29sdXRpb24gDQooa2VlcCBpbiBtaW5kIHRoYXQgY29tcHV0aW5nIHRpbWUgcmlzZXMgZXhwb25lbnRpYWxseSB3aXRoIGdyaWQgc2l6ZSkNCmBgYHtyfQ0KIyBkaXN0YW5jZSB0byBjbG9zZXN0IG90aGVyIHBvaW50Og0KZCA8LSBzYXBwbHkoMTpsZW5ndGgoeCksIGZ1bmN0aW9uKGkpDQogICAgICAgICAgICBtaW4oYmVycnlGdW5jdGlvbnM6OmRpc3RhbmNlKHhbaV0sIHlbaV0sIHhbLWldLCB5Wy1pXSkpICkNCiMgZm9yIGxhdC1sb25nIGRhdGEgdXNlICgyMDE3LUFwciBvbmx5IGF2YWlsYWJsZSBpbiBnaXRodWIgdmVyc2lvbiBvZiBPU01zY2FsZSkNCiMgZCA8LSBPU01zY2FsZTo6bWF4RWFydGhEaXN0KHkseCwgZGF0YT1jZW50X2xsLCBmdW49bWluKQ0KaGlzdChkLzEwMDAsIGJyZWFrcz0yMCwgbWFpbj0iZGlzdGFuY2UgdG8gY2xvc2VzdCBnYXVnZSBba21dIikNCm1lYW4oZC8xMDAwKSAjIDgga20NCmBgYA0KDQpQZXJmb3JtIGtyaWdpbmcgb24gYSBncmlkIHdpdGggdGhhdCByZXNvbHV0aW9uIA0KYGBge3J9DQpyZXMgPC0gMTAwMCAjIDEga20sIHNpbmNlIHN0YXRpb25zIGFyZSA4IGttIGFwYXJ0IG9uIGF2ZXJhZ2UNCmdyaWQgPC0gZXhwYW5kLmdyaWQoc2VxKG1pbih4KSxtYXgoeCkscmVzKSwNCiAgICAgICAgICAgICAgICAgICAgc2VxKG1pbih5KSxtYXgoeSkscmVzKSkNCmtyaWNvIDwtIGtyaWdlLmNvbnRyb2wodHlwZS5rcmlnZT0iT0siLCBvYmoubW9kZWw9Zml0KQ0KI2tyb2JqIDwtIGtyaWdlLmNvbnYoZ2VvcHJlYywgbG9jPWdyaWQsIGtyaWdlPWtyaWNvKQ0KI3NhdmUoa3JvYmosIGZpbGU9ImRhdGEva3JvYmouUmRhdGEiKQ0KbG9hZCgiZGF0YS9rcm9iai5SZGF0YSIpICMgbGluZSBhYm92ZSBpcyB0b28gc2xvdyBmb3IgcmVjcmVhdGlvbiBlYWNoIHRpbWUNCmBgYA0KDQpQbG90IHRoZSBpbnRlcnBvbGF0ZWQgdmFsdWVzIHdpdGggYGltYWdlYCBvciBhbiBlcXVpdmFsZW50IGZ1bmN0aW9uIA0KKHNlZSBbUmNsaWNrXShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yY2xpY2spIDQuMTUpIGFuZCBhZGQgY29udG91ciBsaW5lcy4NCmBgYHtyfQ0KcGFyKG1hcj1jKDAsMywwLDMpKQ0KZ2VvUjo6OmltYWdlLmtyaWdpbmcoa3JvYmosIGNvbD1wY29sKQ0KY29sUG9pbnRzKHgsIHksIHJhaW4kUDEsIGNvbD1wY29sLCBsZWdhcmdzPWxpc3QoaG9yaXo9RiwgdGl0bGU9IlByZWMiLHkxPTAuMSx4MT0wLjkpKQ0KcG9pbnRzKHgseSkNCnBsb3QocmFpbiwgY29sPU5BLCBhZGQ9VFJVRSkNCmBgYA0KXA0KW3RvcF0oI3RvcCkNCg0KIyBEaXNjaGFyZ2UNClJpdmVyIGRpc2NoYXJnZSB0aW1lLXNlcmllcyB2aXN1YWxpc2F0aW9uIGFuZCBleHRyZW1lIHZhbHVlIHN0YXRpc3RpY3MgKGBhbmltYXRpb25gICsgYGV4dHJlbWVTdGF0YCkgIA0KKipCZXJyeSBCb2Vzc2Vua29vbCoqDQoNCiMjIyBSZWFkLCBwbG90IGFuZCBhZ2dyZWdhdGUgZGF0YQ0KDQpEYXRhc2V0cyBmcm9tIDxodHRwOi8vbnJmYS5jZWguYWMudWsvZGF0YS9zdGF0aW9uL21lYW5mbG93LzM5MDcyPiAgDQpEb3dubG9hZCANCltkaXNjaGFyZ2UxXShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vdHJlZS9tYXN0ZXIvcHJlc2VudGF0aW9ucy9kYXRhL2Rpc2NoYXJnZTM5MDcyLmNzdiksDQpbZGlzY2hhcmdlMl94eHhdKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby90cmVlL21hc3Rlci9wcmVzZW50YXRpb25zL2RhdGEvZGlzY2hhcmdlLmNzdiksIA0KW2Rpc2NoYXJnZTNfeHh4XShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vdHJlZS9tYXN0ZXIvcHJlc2VudGF0aW9ucy9kYXRhL2Rpc2NoYXJnZS5jc3YpLCANCm9yIGJldHRlcjogW2Rvd25sb2FkIHRoZSB3aG9sZSBnaXRodWIgY291cnNlIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby9hcmNoaXZlL21hc3Rlci56aXApDQoNClJlYWQgYW5kIHRyYW5zZm9ybSBkYXRhDQpgYGB7cn0NClEgPC0gcmVhZC50YWJsZSgiZGF0YS9kaXNjaGFyZ2UzOTA3Mi5jc3YiLCBza2lwPTE5LCBoZWFkZXI9VFJVRSwgc2VwPSIsIiwgZmlsbD1UUlVFKVssMToyXQ0KY29sbmFtZXMoUSkgPC0gYygiZGF0ZSIsIlJveWFsX1dpbmRzb3JfUGFyayIpDQpRJGRhdGUgPC0gYXMuRGF0ZShRJGRhdGUsIGZvcm1hdD0iJVktJW0tJWQiKQ0KYGBgDQoNCkV4YW1pbmUgZGF0YQ0KYGBge3J9DQpoZWFkKFEpDQpgYGANCg0KYGBge3J9DQpzdHIoUSkNCmBgYA0KDQpTaW1wbGUgdGltZSBzZXJpZXMgcGxvdA0KYGBge3J9DQpwbG90KFEsIHR5cGU9ImwiLCBjb2w9ImJsdWUiKQ0KYGBgDQoNClB1YmxpY2F0aW9uLXJlYWR5IGdyYXBoaWNzDQpgYGB7cn0NCnBuZygiRGlzY2hhcmdlVmlzLnBuZyIsIHdpZHRoPTIwLCBoZWlnaHQ9MTAsIHVuaXRzPSJjbSIsIHJlcz01MDApDQojcGRmKCJEaXNjaGFyZ2VWaXMucGRmIiwgd2lkdGg9MjAvMi41LCBoZWlnaHQ9MTAvMi41KSAjIHZlY3RvciBncmFwaA0KcGFyKG1hcj1jKDMuNSwzLjUsMi41LDAuMiksIG1ncD1jKDIuMywwLjcsMCksIGxhcz0xKQ0KcGxvdChRLCB0eXBlPSJsIiwgY29sPSJibHVlIiwgbWFpbj0iTlJGQTogVGhhbWVzXG5Sb3lhbCBXaW5kc29yIFBhcmsiLA0KICAgICB4bGFiPSJEYXRlIiwgeWxhYj0iRGlzY2hhcmdlICBbbVxVezAwQjN9L3NdIikNCmRldi5vZmYoKQ0KYGBgDQoNCkFubnVhbCBtYXhpbWEsIEdlcm1hbiBoeWRyb2xvZ2ljYWwgeWVhciBzcGxpdCBhdCBPY3QgMzENCmBgYHtyfQ0KUSRoeWR5ZWFyIDwtIGFzLm51bWVyaWMoZm9ybWF0KFEkZGF0ZSs2MSwgIiVZIikpDQphbm5tYXggPC0gdGFwcGx5KFEkUm95YWxfV2luZHNvcl9QYXJrLCBRJGh5ZHllYXIsIG1heCwgbmEucm09VFJVRSkNCmFubm1heCA8LSBhbm5tYXhbLTFdDQpoeWR5ZWFyIDwtIGFzLm51bWVyaWMobmFtZXMoYW5ubWF4KSkgDQpwbG90KGh5ZHllYXIsIGFubm1heCwgdHlwZT0ibyIsIGxhcz0xKQ0KYGBgDQoNCiMjIyBFeHRyZW1lIHZhbHVlIHN0YXRpc3RpY3MNCg0KYGBge3J9DQpsaWJyYXJ5KGV4dHJlbWVTdGF0KQ0KZGxmIDwtIGRpc3RMZml0KGFubm1heCkNCnBsb3RMZml0KGRsZikNCnBsb3RMZml0KGRsZiwgY2RmPVRSVUUpDQpkbGUgPC0gZGlzdExleHRyZW1lKGRsZj1kbGYsIFJQcz1jKDUsMTAsNTAsMTAwKSwgZ3BkPUZBTFNFKQ0KcGxvdExleHRyZW1lKGRsZSkNCmBgYA0KDQpMb2dhcml0aG1pYyBwbG90IHdpdGggbWFueSBmaXR0ZWQgZGlzdHJpYnV0aW9uIGZ1bmN0aW9ucw0KYGBge3J9DQpwbG90TGV4dHJlbWUoZGxlLCBuYmVzdD0xNiwgbG9nPVRSVUUpDQpgYGANCg0KUmV0dXJuIHZhbHVlcyAoZGlzY2hhcmdlIGVzdGltYXRlcyBmb3IgZ2l2ZW4gcmV0dXJuIHBlcmlvZHMpDQpgYGB7cn0NCmRsZiRyZXR1cm5sZXYNCmBgYA0KSW4gcmVhbGl0eSwgcGxlYXNlIHVzZSBub24tc3RhdGlvbmFyeSBFVlMhDQoNClVuY2VydGFpbnR5IGJhbmQgZm9yIFdha2VieSBkaXN0cmlidXRpb24gZml0IGVzdGltYXRlDQpgYGB7cn0NCmRsZV9ib290IDwtIGRpc3RMZXhCb290KGRsZSwgbj0xMCwgbmJlc3Q9MSkNCnBsb3RMZXhCb290KGRsZV9ib290LCBkaXN0Y29sPSJncmVlbiIpDQpgYGANCg0KDQpNb3JlIGRldGFpbHMgaW4gdGhlIHBhY2thZ2UgdmlnbmV0dGUNCmBgYHtyfQ0KdmlnbmV0dGUoImV4dHJlbWVTdGF0IikNCmBgYA0KDQoNCiMjIyBBbmltYXRlZCBtb3ZpZQ0KDQoNClJlYWQgZGF0YSBmcm9tIHNldmVyYWwgZGlzY2hhcmdlIHN0YXRpb25zDQpgYGB7cn0NCmlmKEZBTFNFKXsNClEyIDwtIHJlYWQudGFibGUoImRhdGEvZGlzY2hhcmdlX19fX18uY3N2Iiwgc2tpcD0xOSwgaGVhZGVyPVRSVUUsIHNlcD0iLCIsIGZpbGw9VFJVRSlbLDE6Ml0NCmNvbG5hbWVzKFEyKSA8LSBjKCJkYXRlIiwiZHVtbXkxIik7ICBRMiRkYXRlIDwtIGFzLkRhdGUoUTIkZGF0ZSwgZm9ybWF0PSIlWS0lbS0lZCIpDQpRMyA8LSByZWFkLnRhYmxlKCJkYXRhL2Rpc2NoYXJnZV9fX19fLmNzdiIsIHNraXA9MTksIGhlYWRlcj1UUlVFLCBzZXA9IiwiLCBmaWxsPVRSVUUpWywxOjJdDQpjb2xuYW1lcyhRMykgPC0gYygiZGF0ZSIsImR1bW15MiIpOyAgUTMkZGF0ZSA8LSBhcy5EYXRlKFEzJGRhdGUsIGZvcm1hdD0iJVktJW0tJWQiKQ0KfQ0KUTIgPC0gUSA7IFEyWywyXSA8LSBRMlssMl0gKyAxMDAgOyBRMiA8LSBRMlstKDE6MTAwKSxdDQpRMyA8LSBRIDsgUTNbLDJdIDwtIFEzWywyXSAtICA1MCA7IFEzIDwtIGhlYWQoUTMsIC0zNjUqMykNCmNvbG5hbWVzKFEyKVsyXSA8LSAiZHVtbXkxIjsgY29sbmFtZXMoUTMpWzJdIDwtICJkdW1teTIiDQpgYGANCg0KTWVyZ2UgZGF0YXNldHMNCmBgYHtyfQ0KZGlzIDwtIFJlZHVjZShmdW5jdGlvbiguLi4pIG1lcmdlKC4uLiwgYWxsPVQpLCBsaXN0KFEsUTIsUTMpKQ0KYGBgDQoNCkNvZGUgdG8gZ2VuZXJhdGUgb25lIG1vdmllIHNsaWNlDQpgYGB7cn0NCmxpYnJhcnkoYmVycnlGdW5jdGlvbnMpICMgZm9yIGxpbTAsIG1vbnRoQXhpcywgdGV4dEZpZWxkLCBldGMNCg0Kc2NlbmUgPC0gZnVuY3Rpb24oaSwgdmxjbm90ZT1UUlVFLCBjZXg9MS4yKQ0Kew0KIHNlbCA8LSAwOjEyMA0KIGRpczIgPC0gZGlzW2kgKyBzZWwsIF0NCiBzdGF0IDwtIGMoIlJveWFsX1dpbmRzb3JfUGFyayIsICJkdW1teTEiLCAiZHVtbXkyIikNCiBjb2wgPC0gYygicmVkIiwgImJsdWUiLCAib3JhbmdlIikNCiBuYW1lcyhjb2wpIDwtIHN0YXQNCiBwbG90KGRpczIkZGF0ZSxkaXMyJFJveWFsX1dpbmRzb3JfUGFyaywgdHlwZT0ibiIsIHlsaW09bGltMCg1MDApLCAgbGFzPTEsIA0KICAgICAgeGF4dD0ibiIsIHlheHQ9Im4iLCBjZXgubGFiPWNleCwgeGxhYj0iIiwgDQogICAgICB5bGFiPSJEaXNjaGFyZ2UgIFttXFV7MDBCM30vc10iLCB4YXhzPSJpIikNCiBheGlzKDIsIGNleC5heGlzPWNleCwgbGFzPTEpDQogU3lzLnNldGxvY2FsZSgiTENfVElNRSIsICJDIikNCiBtb250aEF4aXMobWlkbW9udGg9VFJVRSwgZm9ybWF0PSIlYlxuJVkiLCBjZXg9Y2V4LCBtZ3A9YygzLDMuNSwwKSkNCiBhYmxpbmUoaD0xOjYqMTAwLCB2PWRpczIkZGF0ZVtmb3JtYXQoZGlzMiRkYXRlLCIlZCIpPT0iMDEiXSwgY29sPTgpDQogZm9yKHMgaW4gc3RhdCkgbGluZXMoZGlzMiRkYXRlLCBkaXMyWyxzXSwgbHdkPTQsIGNvbD1jb2xbc10pDQogeGkgPC0gc2VxUihzZWwsbGVuPWxlbmd0aChzdGF0KSsyKVstMV0NCiB4aSA8LSBoZWFkKHhpLCAtMSkNCiB0ZXh0RmllbGQoZGlzMiRkYXRlW3hpXSwgZGlhZyhhcy5tYXRyaXgoZGlzMlt4aSxzdGF0XSkpLCBzdGF0LCBjZXg9Y2V4LCBjb2w9Y29sKQ0KIGJveCgpDQogaWYodmxjbm90ZSkgbXRleHQoIlZMQzogJ2UnOiBzaW5nbGUgZnJhbWUgZm9yd2FyZFxuJ1NISUZUK0xFRlQnOiBmZXcgc2Vjb25kcyBiYWNrIiwNCiAgICAgICAgICAgICAgICAgICBzaWRlPTMsIGxpbmU9LTksIG91dGVyPVRSVUUsIGFkaj0wLjk1LCBjZXg9Y2V4KQ0KfQ0KDQpwYXIobWFyPWMoNSw1LDAuNSwwLjUpLCBtZ3A9YygzLDAuNywwKSkNCnNjZW5lKDE1MCkNCmBgYA0KDQpBY3R1YWwgbW92aWUNCmBgYHtyLCBldmFsPUZBTFNFfQ0KbGlicmFyeShhbmltYXRpb24pDQpzYXZlVmlkZW8oe3BhcihtYXI9Yyg2LDgsMSwxKSwgbWdwPWMoNS41LDAuNywwKSkNCiBkdW1teSA8LSBwYnNhcHBseShzZXEoMCwgYnk9MywgbGVuPTUwKSwgc2NlbmUsIGNleD0yKTsgcm0oZHVtbXkpDQp9LCB2aWRlby5uYW1lPSJRbW92aWUubXA0IiwgaW50ZXJ2YWw9MC4xLCBmZm1wZWc9Ii91c3IvYmluL2ZmbXBlZyIsIA0KYW5pLndpZHRoPTcqMjAwLCBhbmkuaGVpZ2h0PTUqMjAwKQ0KYGBgDQoNCltPcGVuIHZpZGVvIGluIGRlZmF1bHQgbXA0cGxheWVyXShRbW92aWUubXA0KQ0KDQpcDQpbdG9wXSgjdG9wKQ0KDQojIEh5ZG1vZA0KSHlkcm9sb2dpY2FsIG1vZGVsbGluZyB3aXRoIGBhaXJHUmAgICANCioqS2F0aWUgU21pdGgqKg0KDQpcDQpbdG9wXSgjdG9wKQ0KDQojIEVEQQ0KRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyBpbmNsdWRpbmcgZmxvdyBkdXJhdGlvbiBjdXJ2ZSBhbmQgdHJlbmQgYW5hbHlzaXMgb24gdGltZS1zZXJpZXMgICANCioqU2hhdW4gSGFycmlnYW4qKg0KDQpcDQpbdG9wXSgjdG9wKQ0KDQoNCiMgRGlzY3Vzc2lvbg0KDQpQbGVhc2UgZ2l2ZSB1cyBmZWVkYmFjayBhdA0KPGZvbnQgc2l6ZT0iNiI+W2JpdC5seS9mZWVkYmFja1JdKGh0dHBzOi8vYml0Lmx5L2ZlZWRiYWNrUik8L2ZvbnQ+IA0KDQpGb3IgZGlzY3Vzc2lvbnMsIHBsZWFzZSB1c2UgdGhlIA0KW0h5ZHJvbG9neSBpbiBSIEZhY2Vib29rIGdyb3VwXShodHRwczovL3d3dy5mYWNlYm9vay5jb20vZ3JvdXBzLzExMzAyMTQ3NzcxMjM5MDkvKS4gIA0KDQo=